﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Borg.AddOn;
using Borg.Properties;

namespace Borg
{
    [Serializable]
    public class TypeStore : RuntimeElement, ITypeStoreReader, ITypeStoreObserver
    {
        private readonly MetaElementCollection<Contract> _contracts;
        private readonly MetaElementCollection<Plugin> _plugins;

        protected readonly Dictionary<string, MetaElementCollection<Contract>> contractDictionary;
        protected readonly Dictionary<string, MetaElementCollection<SlotDefinition>> slotDefinitionDictionary;
        protected readonly Dictionary<string, MetaElementCollection<Plugin>> pluginDictionary;
        protected readonly Dictionary<string, MetaElementCollection<ExtensionType>> extensionTypeDictionary;
        protected readonly Dictionary<string, MetaElementCollection<PlugType>> plugTypeDictionary;
        protected readonly Dictionary<string, MetaElementCollection<SlotType>> slotTypeDictionary;

        protected readonly Dictionary<int, Contract> contractIdDictionary;
        protected readonly Dictionary<int, SlotDefinition> slotDefinitionIdDictionary;
        protected readonly Dictionary<int, Plugin> pluginIdDictionary;
        protected readonly Dictionary<int, ExtensionType> extensionTypeIdDictionary;
        protected readonly Dictionary<int, PlugType> plugTypeIdDictionary;
        protected readonly Dictionary<int, SlotType> slotTypeIdDictionary;

        private ITypeStoreNotificationHook _notificationHook;
        private ITypeStorePermissionHook _permissionHook;

        public MetaElementCollection<Contract> Contracts
        {
            get
            {
                if (!CheckAccess())
                    throw GetAccessException();
                
                return _contracts;
            }
        }

        public MetaElementCollection<Plugin> Plugins
        {
            get
            {
                if (!CheckAccess())
                    throw GetAccessException();
                
                return _plugins;
            }
        }

        public IEnumerable<SlotDefinition> SlotDefinitions
        {
            get
            {
                if (!CheckAccess())
                    throw GetAccessException();

                return _contracts.SelectMany(contract => contract.SlotDefinitions);
            }
        }

        public IEnumerable<ExtensionType> ExtensionTypes
        {
            get
            {
                if (!CheckAccess())
                    throw GetAccessException();

                return _plugins.SelectMany(plugin => plugin.ExtensionTypes);
            }
        }

        public IEnumerable<PlugType> PlugTypes
        {
            get
            {
                if (!CheckAccess())
                    throw GetAccessException();

                return from plugin in _plugins
                       from extensionType in plugin.ExtensionTypes
                       from plugType in extensionType.PlugTypes
                       select plugType;
            }
        }

        public IEnumerable<SlotType> SlotTypes
        {
            get
            {
                if (!CheckAccess())
                    throw GetAccessException();

                return from plugin in _plugins
                       from extensionType in plugin.ExtensionTypes
                       from slotType in extensionType.SlotTypes
                       select slotType;
            }
        }

        public event EventHandler<ContractEventArgs> ContractAdded
        {
            add { Notifier.AddContractAddedHandler(value); }
            remove { Notifier.RemoveContractAddedHandler(value); }
        }

        public event EventHandler<ContractEventArgs> ContractRemoved
        {
            add { Notifier.AddContractRemovedHandler(value); }
            remove { Notifier.RemoveContractRemovedHandler(value); }
        }

        public event EventHandler<PluginEventArgs> PluginAdded
        {
            add { Notifier.AddPluginAddedHandler(value); }
            remove { Notifier.RemovePluginAddedHandler(value); }
        }

        public event EventHandler<PluginEventArgs> PluginRemoved
        {
            add { Notifier.AddPluginRemovedHandler(value); }
            remove { Notifier.RemovePluginRemovedHandler(value); }
        }

        protected internal TypeStore(Runtime runtime)
            : base(runtime)
        {
            _contracts = new MetaElementCollection<Contract>();
            _plugins = new MetaElementCollection<Plugin>();
            
            contractDictionary = new Dictionary<string, MetaElementCollection<Contract>>();
            slotDefinitionDictionary = new Dictionary<string, MetaElementCollection<SlotDefinition>>();
            pluginDictionary = new Dictionary<string, MetaElementCollection<Plugin>>();
            extensionTypeDictionary = new Dictionary<string, MetaElementCollection<ExtensionType>>();
            plugTypeDictionary = new Dictionary<string, MetaElementCollection<PlugType>>();
            slotTypeDictionary = new Dictionary<string, MetaElementCollection<SlotType>>();
            
            contractIdDictionary = new Dictionary<int, Contract>();
            slotDefinitionIdDictionary = new Dictionary<int, SlotDefinition>();
            pluginIdDictionary = new Dictionary<int, Plugin>();
            extensionTypeIdDictionary = new Dictionary<int, ExtensionType>();
            plugTypeIdDictionary = new Dictionary<int, PlugType>();
            slotTypeIdDictionary = new Dictionary<int, SlotType>();
        }

        internal void Init(ITypeStoreNotificationHook notificationHook,
            ITypeStorePermissionHook permissionHook)
        {
            _notificationHook = notificationHook;
            _permissionHook = permissionHook;

            notificationHook.Init(TypeStoreModifier);
        }

        public bool ContainsPlugin(Uri uri, string name)
        {
            if (!CheckAccess())
                throw GetAccessException();

            return _plugins.Any(
                plugin =>
                plugin.OriginUri.Equals(uri) && plugin.Name.Equals(name));
        }

        public bool ContainsContract(Uri uri, string name)
        {
            if (!CheckAccess())
                throw GetAccessException();

            return _contracts.Any(contract => contract.OriginUri.
                Equals(uri) && contract.Name.Equals(name));
        }

        public Contract GetContract(int id)
        {
            return
                contractIdDictionary.ContainsKey(id)
                    ? contractIdDictionary[id]
                    : null;
        }

        public SlotDefinition GetSlotDefinition(int id)
        {
            return
                slotDefinitionIdDictionary.ContainsKey(id)
                    ? slotDefinitionIdDictionary[id]
                    : null;
        }

        public Plugin GetPlugin(int id)
        {
            return
                pluginIdDictionary.ContainsKey(id)
                    ? pluginIdDictionary[id]
                    : null;
        }

        public ExtensionType GetExtensionType(int id)
        {
            return
                extensionTypeIdDictionary.ContainsKey(id)
                    ? extensionTypeIdDictionary[id]
                    : null;
        }

        public PlugType GetPlugType(int id)
        {
            return
                plugTypeIdDictionary.ContainsKey(id)
                    ? plugTypeIdDictionary[id]
                    : null;
        }

        public SlotType GetSlotType(int id)
        {
            return
                slotTypeIdDictionary.ContainsKey(id)
                    ? slotTypeIdDictionary[id]
                    : null;
        }

        public MetaElementCollection<Contract> GetContracts(string name)
        {
            if (name == null)
                throw new ArgumentNullException("name");

            if (name == string.Empty)
                throw new ArgumentException(Exceptions.Name_must_not_be_empty, "name");

            if (!CheckAccess())
                throw GetAccessException();
            
            return InternalGetContracts(name);
        }

        public MetaElementCollection<Plugin> GetPlugins(string name)
        {
            if (name == null)
                throw new ArgumentNullException("name");
            
            if (name == string.Empty)
                throw new ArgumentException(Exceptions.Name_must_not_be_empty, "name");
            
            if (!CheckAccess())
                throw GetAccessException();
            
            return InternalGetPlugins(name);
        }

        public MetaElementCollection<SlotDefinition> GetSlotDefinitions(string name)
        {
            if (name == null)
                throw new ArgumentNullException("name");

            if (name == string.Empty)
                throw new ArgumentException(Exceptions.Name_must_not_be_empty, "name");

            if (!CheckAccess())
                throw GetAccessException();
            
            return InternalGetSlotDefinitions(name);
        }

        public MetaElementCollection<ExtensionType> GetExtensionTypes(string name)
        {
            if (name == null)
                throw new ArgumentNullException("name");
            
            if (name == string.Empty)
                throw new ArgumentException(Exceptions.Name_must_not_be_empty, "name");
            
            if (!CheckAccess())
                throw GetAccessException();
            
            return InternalGetExtensionTypes(name);
        }

        public MetaElementCollection<PlugType> GetPlugTypes(string name)
        {
            if (name == null)
                throw new ArgumentNullException("name");
            
            if (name == string.Empty)
                throw new ArgumentException(Exceptions.Name_must_not_be_empty, "name");
            
            if (!CheckAccess())
                throw GetAccessException();
            
            return InternalGetPlugTypes(name);
        }

        public MetaElementCollection<SlotType> GetSlotTypes(string name)
        {
            if (name == null)
                throw new ArgumentNullException("name");

            if (name == string.Empty)
                throw new ArgumentException(Exceptions.Name_must_not_be_empty, "name");

            if (!CheckAccess())
                throw GetAccessException();
            
            return InternalGetSlotTypes(name);
        }

        internal MetaElementCollection<Contract> InternalGetContracts(string name)
        {
            return
                contractDictionary.ContainsKey(name)
                    ? contractDictionary[name]
                    : (contractDictionary[name] =
                       new MetaElementCollection<Contract>());
        }

        internal MetaElementCollection<Plugin> InternalGetPlugins(string name)
        {
            return
                pluginDictionary.ContainsKey(name)
                    ? pluginDictionary[name]
                    : (pluginDictionary[name] =
                       new MetaElementCollection<Plugin>());
        }

        internal MetaElementCollection<SlotDefinition> InternalGetSlotDefinitions(string name)
        {
            return
                slotDefinitionDictionary.ContainsKey(name)
                    ? slotDefinitionDictionary[name]
                    : (slotDefinitionDictionary[name] =
                       new MetaElementCollection<SlotDefinition>());
        }

        internal MetaElementCollection<ExtensionType> InternalGetExtensionTypes(string name)
        {
            return
                extensionTypeDictionary.ContainsKey(name)
                    ? extensionTypeDictionary[name]
                    : (extensionTypeDictionary[name] =
                       new MetaElementCollection<ExtensionType>());
        }

        internal MetaElementCollection<PlugType> InternalGetPlugTypes(string name)
        {
            return
                plugTypeDictionary.ContainsKey(name)
                    ? plugTypeDictionary[name]
                    : (plugTypeDictionary[name] =
                       new MetaElementCollection<PlugType>());
        }

        internal MetaElementCollection<SlotType> InternalGetSlotTypes(string name)
        {
            return
                slotTypeDictionary.ContainsKey(name)
                    ? slotTypeDictionary[name]
                    : (slotTypeDictionary[name] =
                       new MetaElementCollection<SlotType>());
        }

        [Serializable]
        public class Modifier : ITypeStoreModifier
        {
            protected readonly TypeStore store;

            private readonly Queue<TypeStoreTask> _notifyComposerQueue;
            private readonly TypeBuilder _typeBuilder;

            private ITypeStoreModifierApiHook _apiHook;

            private Composer Composer
            {
                get { return store.Runtime.Composer; }
            }

            private Notifier Notifier
            {
                get { return store.Runtime.Notifier; }
            }

            public TypeBuilder TypeBuilder
            {
                get { return _typeBuilder; }
            }

            internal Modifier(TypeStore store)
            {
                this.store = store;
                
                _notifyComposerQueue = new Queue<TypeStoreTask>();
                _typeBuilder = new TypeBuilder(store.Runtime);
            }

            public void Init(ITypeStoreModifierApiHook apiHook)
            {
                _apiHook = apiHook;
            }

            public void Add(Contract contract)
            {
                if (contract == null)
                    throw new ArgumentNullException("contract");

                if (!store.CheckAccess())
                    throw GetAccessException();

                _apiHook.OnModification(TypeStoreModificationOperation.Add, contract);
                LogArgs log;

                if (!InternalCanAdd(contract, out log))
                    throw new InvalidOperationException(
                        string.Format(
                            Messages.TypeStore_ErrorOnAddingContract,
                            (object) contract.Id,
                            (object) contract.Name,
                            (object) contract.OriginUri,
                            log != null
                                ? (object) log.Message
                                : (object) string.Empty));
                
                InternalAdd(contract);
                
                if (Composer.IsBusy)
                    return;
                
                NotifyComposer();
            }

            public void Add(Plugin plugin)
            {
                if (plugin == null)
                    throw new ArgumentNullException("plugin");
                
                if (!store.CheckAccess())
                    throw GetAccessException();
                
                _apiHook.OnModification(TypeStoreModificationOperation.Add, plugin);
                LogArgs log;

                if (!InternalCanAdd(plugin, out log))
                    throw new InvalidOperationException(
                        string.Format(
                            Messages.TypeStore_ErrorOnAddingPlugin,
                            (object) plugin.Id,
                            (object) plugin.Name,
                            (object) plugin.OriginUri,
                            log != null
                                ? (object) log.Message
                                : (object) string.Empty));
                
                InternalAdd(plugin);
                
                if (Composer.IsBusy)
                    return;
                
                NotifyComposer();
            }

            public void Remove(Contract contract)
            {
                if (contract == null)
                    throw new ArgumentNullException("contract");

                if (!store.CheckAccess())
                    throw GetAccessException();
                
                _apiHook.OnModification(TypeStoreModificationOperation.Remove, contract);
                LogArgs log;

                if (!InternalCanRemove(contract, out log))
                    throw new InvalidOperationException(
                        string.Format(
                            Messages.TypeStore_ErrorOnRemovingContract,
                            (object) contract.Id,
                            (object) contract.Name,
                            (object) contract.OriginUri,
                            log != null
                                ? (object) log.Message
                                : (object) string.Empty));

                InternalRemove(contract);

                if (Composer.IsBusy)
                    return;
                
                NotifyComposer();
            }

            public void Remove(Plugin plugin)
            {
                if (plugin == null)
                    throw new ArgumentNullException("plugin");
                
                if (!store.CheckAccess())
                    throw GetAccessException();
                
                _apiHook.OnModification(TypeStoreModificationOperation.Remove, plugin);
                LogArgs log;

                if (!InternalCanRemove(plugin, out log))
                    throw new InvalidOperationException(
                        string.Format(
                            Messages.TypeStore_ErrorOnRemovingPlugin,
                            (object) plugin.Id,
                            (object) plugin.Name,
                            (object) plugin.OriginUri,
                            log != null
                                ? (object) log.Message
                                : (object) string.Empty));
                
                InternalRemove(plugin);
                
                if (Composer.IsBusy)
                    return;
                
                NotifyComposer();
            }

            public void Add(IEnumerable<Contract> contracts, IEnumerable<Plugin> plugins)
            {
                if (!store.CheckAccess())
                    throw GetAccessException();

                LogArgs log;

                if (contracts != null)
                {
                    var cntracts = new List<Contract>();

                    foreach (var contract in contracts)
                    {
                        _apiHook.OnModification(TypeStoreModificationOperation.Add, contract);
                        
                        if (!InternalCanAdd(contract, cntracts, out log))
                            throw new InvalidOperationException(
                                string.Format(
                                    Messages.TypeStore_ErrorOnAddingContract,
                                    (object) contract.Id,
                                    (object) contract.Name,
                                    (object) contract.OriginUri,
                                    log != null
                                        ? (object) log.Message
                                        : (object) string.Empty));
                        
                        cntracts.Add(contract);
                    }
                }

                if (plugins != null)
                {
                    var plugns = new List<Plugin>();

                    foreach (var plugin in plugins)
                    {
                        _apiHook.OnModification(TypeStoreModificationOperation.Add, plugin);

                        if (!InternalCanAdd(plugin, plugns, out log))
                            throw new InvalidOperationException(
                                string.Format(
                                    Messages.TypeStore_ErrorOnAddingPlugin,
                                    (object) plugin.Id,
                                    (object) plugin.Name,
                                    (object) plugin.OriginUri,
                                    log != null
                                        ? (object) log.Message
                                        : (object) string.Empty));
                        
                        plugns.Add(plugin);
                    }
                }

                InternalAdd(contracts, plugins);

                if (Composer.IsBusy)
                    return;
                
                NotifyComposer();
            }

            public void Remove(IEnumerable<Contract> contracts, IEnumerable<Plugin> plugins)
            {
                if (!store.CheckAccess())
                    throw GetAccessException();

                LogArgs log;

                if (contracts != null)
                {
                    foreach (var contract in contracts)
                    {
                        _apiHook.OnModification(TypeStoreModificationOperation.Remove, contract);
                        
                        if (!InternalCanRemove(contract, out log))
                            throw new InvalidOperationException(
                                string.Format(
                                    Messages.TypeStore_ErrorOnRemovingContract,
                                    (object) contract.Id,
                                    (object) contract.Name,
                                    (object) contract.OriginUri,
                                    log != null
                                        ? (object) log.Message
                                        : (object) string.Empty));
                    }
                }

                if (plugins != null)
                {
                    foreach (var plugin in plugins)
                    {
                        _apiHook.OnModification(TypeStoreModificationOperation.Remove, plugin);
                        
                        if (!InternalCanRemove(plugin, out log))
                            throw new InvalidOperationException(
                                string.Format(
                                    Messages.TypeStore_ErrorOnRemovingPlugin,
                                    (object) plugin.Id,
                                    (object) plugin.Name,
                                    (object) plugin.OriginUri,
                                    log != null
                                        ? (object) log.Message
                                        : (object) string.Empty));
                    }
                }

                InternalRemove(contracts, plugins);
                
                if (Composer.IsBusy)
                    return;
                
                NotifyComposer();
            }

            public bool CanAdd(Contract contract, out LogArgs log)
            {
                if (!store.CheckAccess())
                    throw GetAccessException();
                
                return InternalCanAdd(contract, null, out log);
            }

            public bool CanAdd(Contract contract, IEnumerable<Contract> pendingContracts, out LogArgs log)
            {
                if (!store.CheckAccess())
                    throw GetAccessException();
                
                return InternalCanAdd(contract, pendingContracts, out log);
            }

            public bool CanAdd(Plugin plugin, out LogArgs log)
            {
                if (!store.CheckAccess())
                    throw GetAccessException();
                
                return InternalCanAdd(plugin, null, out log);
            }

            public bool CanAdd(Plugin plugin, IEnumerable<Plugin> pendingPlugins, out LogArgs log)
            {
                if (!store.CheckAccess())
                    throw GetAccessException();
                
                return InternalCanAdd(plugin, pendingPlugins, out log);
            }

            public bool CanRemove(Contract contract, out LogArgs log)
            {
                if (!store.CheckAccess())
                    throw GetAccessException();
                
                return InternalCanRemove(contract, out log);
            }

            public bool CanRemove(Plugin plugin, out LogArgs log)
            {
                if (!store.CheckAccess())
                    throw GetAccessException();
                
                return InternalCanRemove(plugin, out log);
            }

            internal void InternalAdd(IEnumerable<Contract> contracts, IEnumerable<Plugin> plugins)
            {
                if (contracts != null)
                    foreach (var contract in contracts)
                        InternalAdd(contract);

                if (plugins == null)
                    return;

                foreach (var plugin in plugins)
                    InternalAdd(plugin);
            }

            internal void InternalRemove(IEnumerable<Contract> contracts, IEnumerable<Plugin> plugins)
            {
                if (contracts != null)
                    foreach (var contract in contracts)
                        InternalRemove(contract);

                if (plugins == null)
                    return;

                foreach (var plugin in plugins)
                    InternalRemove(plugin);
            }

            internal void InternalAdd(Contract contract)
            {
                if (!SetRuntime(contract))
                    return;

                var elementCollection =
                    !store.contractDictionary.ContainsKey(contract.Name)
                        ? (store.contractDictionary[contract.Name]
                           = new MetaElementCollection<Contract>())
                        : store.contractDictionary[contract.Name];

                store._contracts.Add(contract);
                store.contractIdDictionary.Add(contract.Id, contract);
                elementCollection.Add(contract);
                
                foreach (var slotDefinition in contract.SlotDefinitions)
                    InternalAddSlotDefinition(slotDefinition);
                
                store._notificationHook.OnContractAdded(contract);
                Notifier.OnContractAdded(new ContractEventArgs(contract));
                _notifyComposerQueue.Enqueue(new ContractTask(Composer.OnContractAdded, contract));
            }

            internal void InternalAdd(Plugin plugin)
            {
                if (!SetRuntime(plugin))
                    return;

                var elementCollection =
                    !store.pluginDictionary.ContainsKey(plugin.Name)
                        ? (store.pluginDictionary[plugin.Name] =
                           new MetaElementCollection<Plugin>())
                        : store.pluginDictionary[plugin.Name];

                store._plugins.Add(plugin);
                store.pluginIdDictionary.Add(plugin.Id, plugin);
                elementCollection.Add(plugin);
                
                foreach (var extensionType in plugin.ExtensionTypes)
                    InternalAddExtensionType(extensionType);
                
                store._notificationHook.OnPluginAdded(plugin);
                Notifier.OnPluginAdded(new PluginEventArgs(plugin));
                _notifyComposerQueue.Enqueue(new PluginTask(Composer.OnPluginAdded, plugin));
            }

            internal void InternalRemove(Contract contract)
            {
                store._contracts.Remove(contract);
                store.contractIdDictionary.Remove(contract.Id);
                store.contractDictionary[contract.Name].Remove(contract);
                
                foreach (var slotDefinition in contract.SlotDefinitions)
                    InternalRemoveSlotDefinition(slotDefinition);
                
                store._notificationHook.OnContractRemoved(contract);
                Notifier.OnContractRemoved(new ContractEventArgs(contract));
                _notifyComposerQueue.Enqueue(new ContractTask(Composer.OnContractRemoved, contract));
            }

            internal void InternalRemove(Plugin plugin)
            {
                foreach (var extension in plugin.ExtensionTypes.
                    SelectMany(extensionType => plugin.Runtime.InstanceStore.GetExtensions(extensionType)))
                    extension.Destroy();

                store._plugins.Remove(plugin);
                store.pluginIdDictionary.Remove(plugin.Id);
                store.pluginDictionary[plugin.Name].Remove(plugin);
                
                foreach (var extensionType in plugin.ExtensionTypes)
                    InternalRemoveExtensionType(extensionType);
                
                store._notificationHook.OnPluginRemoved(plugin);
                Notifier.OnPluginRemoved(new PluginEventArgs(plugin));
                _notifyComposerQueue.Enqueue(new PluginTask(Composer.OnPluginRemoved, plugin));
            }

            internal void OnCompositionDone()
            {
                if (_notifyComposerQueue.Count <= 0)
                    return;
                
                NotifyComposer();
            }

            private void InternalAddSlotDefinition(SlotDefinition slotDefinition)
            {
                store.slotDefinitionIdDictionary.Add(slotDefinition.Id, slotDefinition);

                (!store.slotDefinitionDictionary.ContainsKey(slotDefinition.Name)
                     ? (store.slotDefinitionDictionary[slotDefinition.Name] =
                        new MetaElementCollection<SlotDefinition>())
                     : store.slotDefinitionDictionary[slotDefinition.Name]).Add(slotDefinition);
            }

            private void InternalAddExtensionType(ExtensionType extensionType)
            {
                store.extensionTypeIdDictionary.Add(extensionType.Id, extensionType);

                (!store.extensionTypeDictionary.ContainsKey(extensionType.Name)
                     ? (store.extensionTypeDictionary[extensionType.Name] =
                        new MetaElementCollection<ExtensionType>())
                     : store.extensionTypeDictionary[extensionType.Name]).Add(extensionType);
                
                foreach (var plugType in extensionType.PlugTypes)
                    InternalAddPlugType(plugType);
                
                foreach (var slotType in extensionType.SlotTypes)
                    InternalAddSlotType(slotType);
            }

            private void InternalAddPlugType(PlugType plugType)
            {
                store.plugTypeIdDictionary.Add(plugType.Id, plugType);

                (!store.plugTypeDictionary.ContainsKey(plugType.Name)
                     ? (store.plugTypeDictionary[plugType.Name] =
                        new MetaElementCollection<PlugType>())
                     : store.plugTypeDictionary[plugType.Name]).Add(plugType);
            }

            private void InternalAddSlotType(SlotType slotType)
            {
                store.slotTypeIdDictionary.Add(slotType.Id, slotType);

                (!store.slotTypeDictionary.ContainsKey(slotType.Name)
                     ? (store.slotTypeDictionary[slotType.Name] =
                        new MetaElementCollection<SlotType>())
                     : store.slotTypeDictionary[slotType.Name]).Add(slotType);
            }

            private void InternalRemoveSlotDefinition(SlotDefinition slotDefinition)
            {
                store.slotDefinitionIdDictionary.Remove(slotDefinition.Id);
                store.slotDefinitionDictionary[slotDefinition.Name].Remove(slotDefinition);
            }

            private void InternalRemoveExtensionType(ExtensionType extensionType)
            {
                store.extensionTypeIdDictionary.Remove(extensionType.Id);
                store.extensionTypeDictionary[extensionType.Name].Remove(extensionType);
                
                foreach (var plugType in extensionType.PlugTypes)
                    InternalRemovePlugType(plugType);
                
                foreach (var slotType in extensionType.SlotTypes)
                    InternalRemoveSlotType(slotType);
            }

            private void InternalRemovePlugType(PlugType plugType)
            {
                store.plugTypeIdDictionary.Remove(plugType.Id);
                store.plugTypeDictionary[plugType.Name].Remove(plugType);
            }

            private void InternalRemoveSlotType(SlotType slotType)
            {
                store.slotTypeIdDictionary.Remove(slotType.Id);
                store.slotTypeDictionary[slotType.Name].Remove(slotType);
            }

            internal bool InternalCanAdd(Contract contract, IEnumerable<Contract> pendingContracts, out LogArgs log)
            {
                if (!InternalCanAdd(contract, out log))
                    return false;
                
                if (pendingContracts != null)
                {
                    foreach (var contrct in pendingContracts.
                        Where(contrct => contract.Name == contrct.Name &&
                            contract.OriginUri.Equals(contrct.OriginUri)))
                    {
                        log = new LogArgs(
                            LogType.Error,
                            this,
                            contract == contrct
                                ? Messages.TypeStore_Error_ElementAlreadyInTypeStore
                                : Messages.TypeStore_Error_CannotAddElementWithTheSameNameAndUriTwice,
                            new object[] {"Contract"});

                        return false;
                    }
                }

                return true;
            }

            internal bool InternalCanAdd(Contract contract, out LogArgs log)
            {
                if (store.contractDictionary.ContainsKey(contract.Name))
                {
                    foreach (var cntract in store.contractDictionary[contract.Name].
                        Where(cntract => contract.OriginUri.Equals(cntract.OriginUri)))
                    {
                        log = new LogArgs(
                            LogType.Error,
                            this,
                            contract == cntract
                                ? Messages.TypeStore_Error_ElementAlreadyInTypeStore
                                : Messages.TypeStore_Error_TypeStoreAlreadyContainsElementWithThisNameAndUri,
                            new object[] { "Contract" });

                        return false;
                    }
                }

                log = null;
                
                return store._permissionHook.CanAdd(contract, out log);
            }

            internal bool InternalCanAdd(Plugin plugin, IEnumerable<Plugin> pendingPlugins, out LogArgs log)
            {
                if (!InternalCanAdd(plugin, out log))
                    return false;

                if (pendingPlugins != null)
                {
                    foreach (var plugn in pendingPlugins.
                        Where(plugin1 => plugin.Name == plugin1.Name &&
                            plugin.OriginUri.Equals(plugin1.OriginUri)))
                    {
                        log = new LogArgs(
                            LogType.Error,
                            this,
                            plugin == plugn
                                ? Messages.TypeStore_Error_ElementAlreadyInTypeStore
                                : Messages.TypeStore_Error_CannotAddElementWithTheSameNameAndUriTwice,
                            new object[] { "Plugin" });

                        return false;
                    }
                }

                return true;
            }

            internal bool InternalCanAdd(Plugin plugin, out LogArgs log)
            {
                if (store.pluginDictionary.ContainsKey(plugin.Name))
                {
                    foreach (var plugn in store.pluginDictionary[plugin.Name].
                        Where(plugn => plugin.OriginUri.Equals(plugn.OriginUri)))
                    {
                        log = new LogArgs(
                            LogType.Error,
                            this,
                            plugin == plugn
                                ? Messages.TypeStore_Error_ElementAlreadyInTypeStore
                                : Messages.TypeStore_Error_TypeStoreAlreadyContainsElementWithThisNameAndUri,
                            new object[] { "Plugin" });

                        return false;
                    }
                }

                return store._permissionHook.CanAdd(plugin, out log);
            }

            internal bool InternalCanRemove(Contract contract, out LogArgs log)
            {
                if (store.Runtime.Composer.IsBusy)
                {
                    log = new LogArgs(
                        LogType.Error,
                        this,
                        Messages.TypeStore_Error_ElementCannotBeRemovedWhileComposerIsBusy,
                        new object[] { "Contract" });

                    return false;
                }
                
                if (!store.contractDictionary.ContainsKey(contract.Name) ||
                    !store.contractDictionary[contract.Name].Contains(contract))
                {
                    log = new LogArgs(
                        LogType.Error,
                        this,
                        Messages.TypeStore_Error_ElementIsNotInTypeStore,
                        new object[] { "Contract" });

                    return false;
                }
                
                log = null;

                return true;
            }

            internal bool InternalCanRemove(Plugin plugin, out LogArgs log)
            {
                if (store.Runtime.Composer.IsBusy)
                {
                    log = new LogArgs(
                        LogType.Error,
                        this,
                        Messages.TypeStore_Error_ElementCannotBeRemovedWhileComposerIsBusy,
                        new object[] { "Plugin" });

                    return false;
                }
                
                if (!store.pluginDictionary.ContainsKey(plugin.Name) ||
                    !store.pluginDictionary[plugin.Name].Contains(plugin))
                {
                    log = new LogArgs(
                        LogType.Error,
                        this,
                        Messages.TypeStore_Error_ElementIsNotInTypeStore,
                        new object[] { "Plugin" });

                    return false;
                }
                
                log = null;
                
                return true;
            }

            private bool SetRuntime(AssemblyElement element)
            {
                if (element.runtime != null && element.runtime != store.Runtime)
                    return false;

                element.runtime = store.Runtime;

                return true;
            }

            private void NotifyComposer()
            {
                Debug.Assert(!Composer.IsBusy);
                Composer.BeginComposition();
                
                while (_notifyComposerQueue.Count > 0)
                    _notifyComposerQueue.Dequeue().Execute();
                
                Composer.EndComposition(false);
            }
        }
    }
}
